package ink.xiaobaibai.rbac;

import ink.xiaobaibai.annotation.InsidePublicResources;
import ink.xiaobaibai.annotation.OutsidePublicResources;
import ink.xiaobaibai.autoConfig.roleResources.ResourcesRoleMapping;
import ink.xiaobaibai.common.UriMappingRoleName;
import ink.xiaobaibai.service.IAdminUserRoleService;
import ink.xiaobaibai.units.UriTrie;
import org.springframework.aop.SpringProxy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @description: 获取当前请求需要的身份
 * @author: 小白白
 * @create: 2021-05-14
 **/

@Component
public class FilterInvocationSecurityMetadataSourceImpl implements FilterInvocationSecurityMetadataSource, ApplicationRunner {

    public static String PUBLIC_SOURCE = "ALL";
    public static String PRIVATE_SOURCE = "IN";

    @Autowired
    private IAdminUserRoleService userRoleService;

    @Autowired
    private ApplicationContext applicationContext;

    @Value("${server.servlet.context-path}")
    private String baseUri;
    /**
     * 当前uri资源对应的角色
     */
    private Map<String, Set<ConfigAttribute>> map = new HashMap<>();
    private Set<String> outsidePublicResourcesSet;
    private Set<String> insidePublicResourcesSet;
    private static Collection<ConfigAttribute> outResourcesCollection =
            Collections.singletonList(new SecurityConfig(PUBLIC_SOURCE));
    private static Collection<ConfigAttribute> inResourcesCollection =
            Collections.singletonList(new SecurityConfig(PRIVATE_SOURCE));
    private static Set<ConfigAttribute> outResourcesSet = Collections.singleton(new SecurityConfig(PUBLIC_SOURCE));
    private static Set<ConfigAttribute> inResourcesSet = Collections.singleton(new SecurityConfig(PRIVATE_SOURCE));
    private UriTrie uriTrie;

    @Override
    public void run(ApplicationArguments args) {
        //获取各个资源需要的角色
        /**
         * 如何快速获取?
         * 1.先查出所有的资源,得到Map-> id-uri
         * 2.查出所有的角色-资源映射,得到Map-> roleId-resourcesId
         * 3.然后先替换一波,得到Map-> uri-roleId
         * 4.查出所有的角色信息,得到Map-> roleId-roleName
         * 5.最后再替换一波,得到Map-> uri-roleName
         *
         * 搞个快速联表查询来
         */

        Map<String, SecurityConfig> securityConfigMap = new HashMap<>();

        List<UriMappingRoleName> uriMappingRoleNameList = this.userRoleService.getUriMappingRoleName();

        List<String> uriList = uriMappingRoleNameList.stream().map(UriMappingRoleName::getResourcesUri).collect(Collectors.toList());
        this.uriTrie = new UriTrie(uriList);

        for (UriMappingRoleName uriMappingRoleName : uriMappingRoleNameList) {
            if (uriMappingRoleName.getResourcesUri() == null) {
                continue;
            }
            Set<ConfigAttribute> set;
            //汇集uri-角色名映射
            if (this.map.get(uriMappingRoleName.getResourcesUri()) == null) {
                set = new HashSet<>();
                this.map.put(uriMappingRoleName.getResourcesUri(), set);
            } else {
                set = this.map.get(uriMappingRoleName.getResourcesUri());
            }
            SecurityConfig securityConfig = securityConfigMap.get(uriMappingRoleName.getRoleName());
            if (securityConfig == null) {
                securityConfig = new SecurityConfig(uriMappingRoleName.getRoleName());
                securityConfigMap.put(uriMappingRoleName.getRoleName(), securityConfig);
                set.add(securityConfig);
            } else {
                set.add(securityConfig);
            }
        }
        //外部公开资源
        this.outsidePublicResourcesSet = new HashSet<>();
        //内部公开资源
        this.insidePublicResourcesSet = new HashSet<>();
        this.findResources();
        this.outsidePublicResourcesSet.add("/error");
    }

    /**
     * 寻找开放资源和内部资源
     * 注意本类大坑: 被代理的类,它方法上的注解将会被抹去,应该找它的原始类上的注解。
     */
    private void findResources() {
        //通过注解去找公共资
        //被RestController修饰的bean
        Map<String, Object> beans = this.applicationContext.getBeansWithAnnotation(RestController.class);
        for (Map.Entry<String, Object> entry : beans.entrySet()) {
//            System.out.println(entry.getValue().getClass().getName());
            Class<?> beanClass = entry.getValue().getClass();

            for (Class<?> anInterface : beanClass.getInterfaces()) {
                if (anInterface == SpringProxy.class) {
                    //当前类是代理对象,将其转为原对象
                    beanClass = beanClass.getSuperclass();
                }
            }

            //类上的uri
            String[] secondaryUris = this.getRequestMappingValuesWithClass(beanClass);
            Method[] methods = beanClass.getDeclaredMethods();
//            System.out.println(beanClass.getName());
            for (Method method : methods) {
                if (method.getAnnotation(OutsidePublicResources.class) != null
                        && method.getAnnotation(InsidePublicResources.class) != null) {
                    throw new RuntimeException("内部公开和外部公开不可同时存在");
                }
                //获取方法上的uri
                String[] uris = this.getRequestMappingValuesWithMethod(method);
                //(方法公开||类上公开)
                if (method.getAnnotation(OutsidePublicResources.class) != null ||
                        beanClass.getAnnotation(OutsidePublicResources.class) != null) {
                    this.addInOrOutSet(uris, secondaryUris, outResourcesSet, this.outsidePublicResourcesSet);
                }

                if (method.getAnnotation(InsidePublicResources.class) != null ||
                        beanClass.getAnnotation(InsidePublicResources.class) != null) {

                    //小于角色映射注解
                    if (method.getAnnotation(ResourcesRoleMapping.class) == null && beanClass.getAnnotation(ResourcesRoleMapping.class) == null) {
                        this.addInOrOutSet(uris, secondaryUris, inResourcesSet, this.insidePublicResourcesSet);
                    }

                }

            }
        }
    }

    private void addInOrOutSet(String[] uris, String[] secondaryUris, Set<ConfigAttribute> set1, Set<String> set2) {
        for (String uri : uris) {
            boolean isPath = false;
            //rest风格
            if (uri.contains("{")) {
                //不加入到set中,应该加入到map中
                int start = uri.indexOf('{');
                uri = uri.substring(0, start) + "*";
                isPath = true;
            }
            for (String s : secondaryUris) {
                if (s.contains("{")) {
                    throw new RuntimeException("当前不支持在类上使用rest风格");
                }
                if (isPath) {
                    this.map.put(this.baseUri + s + uri, set1);
                } else {
                    set2.add(this.baseUri + s + uri);
                }
            }
        }
    }

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        HttpServletRequest request = (HttpServletRequest) o;
        String uri = request.getRequestURI();
        if (uri.length() == 0) {
            return inResourcesCollection;
        }

        //rest风格的资源呢?
        //rest风格的资源-> out: 不加入outSet中,也不去msql中设置即可(mysql中本就不设置)
        //rest风格的资源-> in:  不加入inSet中,可以将{}代替成*放入map中
        if (this.outsidePublicResourcesSet.contains(uri)) {
            //是公开的资源
            return outResourcesCollection;
        }
        if (this.insidePublicResourcesSet.contains(uri)) {
            return inResourcesCollection;
        }

        /**
         *
         * 本质原因: 数据库中 /** 传来 /user/1
         *
         */

        String resultResource = this.uriTrie.getBaseUri(uri);

        Set<ConfigAttribute> roleSet = null;
        //开始谁长用谁的,头部就是最长的
        if (resultResource != null) {
            roleSet = this.map.get(resultResource);
        }

        //为空,说明当前资源没有对应的角色 公开
        return roleSet == null ? outResourcesCollection : roleSet;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }


    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }

    public String[] getRequestMappingValuesWithClass(Class clazz) {
        // get post delete put
        RequestMapping requestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
        if (requestMapping != null && requestMapping.value().length != 0) {
            return requestMapping.value();
        }
        return new String[]{""};
    }

    @SuppressWarnings("all")
    public String[] getRequestMappingValuesWithMethod(Method method) {
        // get post delete put
        GetMapping getAnnotation = method.getAnnotation(GetMapping.class);
        if (getAnnotation != null) {
            return getAnnotation.path().length == 0 ? getAnnotation.value() : getAnnotation.path();
        }
        PostMapping postAnnotation = method.getAnnotation(PostMapping.class);
        if (postAnnotation != null) {
            return postAnnotation.path().length == 0 ? postAnnotation.value() : postAnnotation.path();
        }
        DeleteMapping deleteAnnotation = method.getAnnotation(DeleteMapping.class);
        if (deleteAnnotation != null) {
            return deleteAnnotation.path().length == 0 ? deleteAnnotation.value() : deleteAnnotation.path();
        }
        PutMapping putAnnotation = method.getAnnotation(PutMapping.class);
        if (putAnnotation != null) {
            return putAnnotation.path().length == 0 ? putAnnotation.value() : putAnnotation.path();
        }
        return new String[]{};
    }

}
